package net.sf.odinms.exttools.doclet;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;
import com.sun.javadoc.*;
import com.sun.tools.doclets.internal.toolkit.AnnotationTypeWriter;
import com.sun.tools.doclets.internal.toolkit.ClassWriter;
import com.sun.tools.doclets.internal.toolkit.PackageSummaryWriter;
import com.sun.tools.doclets.internal.toolkit.taglets.DocRootTaglet;
import com.sun.tools.doclets.internal.toolkit.util.DirectoryManager;
import com.sun.tools.doclets.internal.toolkit.util.DocletAbortException;
import com.sun.tools.doclets.internal.toolkit.util.Util;

/**
 * Includes Javadoc format-specific XHTML functionality and convenience methods.
 * It makes more sense to put these methods and properties here rather than in
 * the parent class, since they relate to specific Javadoc documentation page
 * formats. This allows {@link AbstractXhtmlWriter} to be more versatile.
 */
@SuppressWarnings("unchecked")
public abstract class AbstractPageWriter extends AbstractXhtmlWriter {
	
	/**
	 * The configuration associated with this execution of Javadoc. The same
	 * config object applies to all pages being written
	 */
	protected static final Config CONF = Config.getInstance();
	
	/**
	 * Used to differentiate XHTML pages created by various subclasses. Used in
	 * the id attribute of &lt;body&gt; tags for specific page types
	 */
	@SuppressWarnings("hiding")
	protected interface PageType {
		/** Page with listing of all packages (if used) and project summary. */
		String OVERVIEW = "OVERVIEW";
		/** Page containing links to all classes included in the Javadocs. */
		String ALL_CLASSES = "ALL_CLASSES";
		/** Page containing links to all packages (if packages are used). */
		String ALL_PACKAGES = "ALL_PACKAGES";
		/** Page containing all classes, etc. belonging to a package. */
		String PACKAGE_FRAME = "PACKAGE_FRAME";
		
		/** Page detailing the inheritance hierarchy for all packages. */
		String OVERVIEW_TREE = "OVERVIEW_TREE";
		/** Page detailing the inheritance hierarchy for a given package. */
		String PACKAGE_TREE = "PACKAGE_TREE";
		
		/** Page detailing all known usage of a given package. */
		String PACKAGE_USE = "PACKAGE_USE";
		/** Page detailing all known usage of a given class. */
		String CLASS_USE = "CLASS_USE";
		/** Page describing deprecated classes, fields, methods, etc. */
		String DEPRECATED = "DEPRECATED";
		/** Page containing an index of all symbols in the Javadocs. */
		String INDEX = "INDEX";
		/** Page describing details of the serialized form of classes. */
		String SERIALIZED = "SERIALIZED";
		/** Page describing constant values, those declared as "final". */
		String CONSTANTS = "CONSTANTS";
		/** Page describing the different sections of the Javadoc. */
		String HELP = "HELP";
		
		/** Page describing a Java {@link java.lang.Package package}. */
		String PACKAGE = "PACKAGE";
		/** Page describing a Java {@link java.lang.Class class}. */
		String CLASS = "CLASS";
		/** Page describing a Java interface. */
		String INTERFACE = "INTERFACE";
		/** Page describing a Java {@link java.lang.Enum enumeration}. */
		String ENUM = "ENUM";
		/** Page describing a Java {@link java.lang.Exception exception}. */
		String EXCEPTION = "EXCEPTION";
		/** Page describing a Java {@link java.lang.Error error}. */
		String ERROR = "ERROR";
		/** Page describing a Java annotation. */
		String ANNOTATION = "ANNOTATION";
	}
	
	/** Localized strings for class categories. Contains */
	private enum ClassType {
		INTERFACE(CONF.propertyText("Interface"),
				CONF.propertyText("interface")),
		CLASS(CONF.propertyText("Class"), CONF.propertyText("class")),
		EXCEPTION(CONF.propertyText("Exception"),
				CONF.propertyText("exception")),
		ERROR(CONF.propertyText("Error"), CONF.propertyText("error")),
		ENUM(CONF.propertyText("Enum"), CONF.propertyText("enum")),
		ANNOTATION_TYPE(CONF.propertyText("Annotation_Type"),
				CONF.propertyText("annotation_type"));
		
		private String heading;
		private String cssClass;
		
		/**
		 * Private constructor for creating ClassType enum items.
		 * 
		 * @param heading The lead-capitalized text to use for headings.
		 * @param cssClass The lowercase, no-space text to use for CSS.
		 */
		private ClassType(String heading, String cssClass) {
			this.heading = heading;
			this.cssClass = cssClass;
		}
		
		/**
		 * Get the heading string for a particular enum value.
		 * 
		 * @return The lead-capitalized text to use for headings.
		 */
		public String heading() {
			return heading;
		}
		
		/**
		 * Get the CSS class string for a particular enum value.
		 * 
		 * @return The lowercase, no-space text to use for CSS.
		 */
		public String cssClass() {
			return cssClass;
		}
	}
	
	/** Applies to the id attribute of &lt;body&gt; tags for specific page types. */
	protected String pageType;
	
	/**
	 * Path from document root to file being generated; ends with a slash. Empty
	 * if the file is in the destination directory.
	 */
	// TODO Try to use this in replaceDocRootDir(String)?
	protected String pathFromDocRoot = "";
	
	/**
	 * Path from file being generated back to document root; ends with a slash.
	 * For example, if the file generated is "java/lang/Object.html", then the
	 * relative path string is "../../". Empty if the file is in the destination
	 * directory.
	 */
	protected String pathToDocRoot = "";
	
	/**
	 * Same as relativePath, but normalized to never be empty or end with a
	 * slash.
	 * 
	 * @deprecated
	 */
	protected String docRootPathNoSlash = "";
	
	/** The window title for this file. */
	protected String windowTitle;
	
	// These are intended to reduce redundant property lookups
	/** Localized text for "Overview". */
	protected static final String OVERVIEW = CONF.propertyText("Overview");
	/** Localized text for "Package". */
	protected static final String PACKAGE = CONF.propertyText("Package");
	/** Localized text for "Class". */
	protected static final String CLASS = CONF.propertyText("Class");
	/** Localized text for "Tree". */
	protected static final String TREE = CONF.propertyText("Tree");
	/** Localized text for "Use". */
	protected static final String USE = CONF.propertyText("Use");
	/** Localized text for "Deprecated". */
	protected static final String DEPRECATED = CONF.propertyText("Deprecated");
	/** Localized text for "Index". */
	protected static final String INDEX = CONF.propertyText("Index");
	/** Localized text for "Help". */
	protected static final String HELP = CONF.propertyText("Help");
	/** Localized text for Overview tool tip in nav bar. */
	protected static final String OVERVIEW_TIP =
			CONF.propertyText("navtitle.Overview");
	/** Localized text for Deprecated tool tip in nav bar. */
	protected static final String DEPRECATED_TIP =
			CONF.propertyText("navtitle.Deprecated");
	/** Localized text for Index tool tip in nav bar. */
	protected static final String INDEX_TIP =
			CONF.propertyText("navtitle.Index");
	/** Localized text for Help tool tip in nav bar. */
	protected static final String HELP_TIP = CONF.propertyText("navtitle.Help");
	/** Localized text for "Frames". */
	protected static final String FRAMES = CONF.propertyText("Frames");
	/** Localized text for "No Frames". */
	protected static final String NOFRAMES = CONF.propertyText("NoFrames");
	/** Localized text for "All Classes". */
	protected static final String ALLCLASSES = CONF.propertyText("AllClasses");
	/** Localized text for "Summary". */
	protected static final String SUMMARY = CONF.propertyText("Summary");
	/** Localized text for "Detail". */
	protected static final String DETAIL = CONF.propertyText("Detail");
	
	/** Localized text for "Previous Package". */
	protected static final String PREV_PKG = CONF.propertyText("PrevPackage");
	/** Localized text for "Next Package". */
	protected static final String NEXT_PKG = CONF.propertyText("NextPackage");
	/** Localized text for "Previous Class". */
	protected static final String PREV_CLASS = CONF.propertyText("PrevClass");
	/** Localized text for "Next Class". */
	protected static final String NEXT_CLASS = CONF.propertyText("NextClass");
	
	/**
	 * Is a ClassDoc a class, interface, enum, exception, error, or annotation?
	 * 
	 * @param doc The "class" to examine for the true type.
	 * @param cssClass Whether to return an all-lowercase value for a CSS class.
	 * @return A string representing the class sub-type, or null if doc is null.
	 */
	protected String classFlavor(ClassDoc doc, boolean cssClass) {
		if (doc == null)
			return null;
		ClassType type;
		if (doc.isInterface())
			type = ClassType.INTERFACE;
		else if (doc.isEnum())
			type = ClassType.ENUM;
		else if (doc.isException())
			type = ClassType.EXCEPTION;
		else if (doc.isError())
			type = ClassType.ERROR;
		else if (doc.isClass())
			type = ClassType.CLASS;
		else
			type = ClassType.ANNOTATION_TYPE;
		
		// Capitalize first letter if it will be used as an HREF title
		return (cssClass) ? type.cssClass() : type.heading();
	}
	
	/**
	 * For use only by subclasses. Designates that a file is to be written at
	 * the document root level.
	 * 
	 * @param filename The desired name of the file (with extension).
	 * @exception IOException If {@link java.io.FileOutputStream} creation
	 *            fails.
	 * @see #AbstractPageWriter(String, String)
	 */
	protected AbstractPageWriter(String filename) throws IOException {
		this(filename, "");
	}
	
	/**
	 * For use only by subclasses. Designates a filename and location for
	 * writing a new XHTML Javadoc page.
	 * 
	 * @param filename The desired name of the file (with extension).
	 * @param pathToFile The path from the document root to the given file.
	 * @exception IOException If {@link java.io.FileOutputStream} creation
	 *            fails.
	 */
	protected AbstractPageWriter(String filename, String pathToFile)
			throws IOException {
		super(CONF.destDirName + pathToFile, filename, null);
		CONF.currentPkg = null; // Set in subclass constructors as needed.
		pathFromDocRoot = pathToFile;
		pathToDocRoot = findInversePath(pathToFile);
		
		// TODO Make printing name of file being generated optional.
		// System.out.println("* " + pathFromDocRoot + filename);
	}
	
	/**
	 * Given a relative path, find inverse path to return to origin. For
	 * example, if <code>path</code> is "java/lang/", return "../../".
	 * 
	 * @param path The path to invert.
	 * @return The inverse of the path.
	 */
	private String findInversePath(String path) {
		if (path == null || path.length() == 0) {
			return "";
		}
		StringBuffer pathBuffer = new StringBuffer();
		for (int i = 0; i < path.length(); i++) {
			char ch = path.charAt(i);
			if (ch == '/') {
				pathBuffer.append("../");
			}
		}
		return pathBuffer.toString();
	}
	
	/**
	 * Defaults to no keyword set, but with printing the window title script.
	 */
	protected final void printXhtmlHeader() {
		printXhtmlHeader(null, true, null);
	}
	
	/**
	 * Generate all header information for XHTML 1.0 Transitional pages. Print
	 * the XHTML file header, page title, and stylesheet default properties.
	 * 
	 * @param metaKeywords Array of keywords for meta tag. Each element of the
	 *        array is assigned to a separate meta tag. Pass in null for no
	 *        array.
	 * @param windowTitleScript true if printing windowtitle script, false for
	 *        files that appear in the left-hand frames.
	 */
	protected final void printXhtmlHeader(String[] metaKeywords,
			boolean windowTitleScript) {
		printXhtmlHeader(metaKeywords, windowTitleScript, null);
	}
	
	/**
	 * Generate all header information for XHTML 1.0 Transitional pages. Print
	 * the XHTML file header, page title, and stylesheet default properties.
	 * 
	 * @param metaKeywords Array of String keywords for meta tag. Each element
	 *        of the array is assigned to a separate meta tag. Pass in null for
	 *        no array.
	 * @param windowTitleScript true if printing windowtitle script, false for
	 *        files that appear in the left-hand frames.
	 * @param customJavaScript Any custom JavaScript that should appear between
	 *        &lt;script&gt;s
	 */
	protected final void printXhtmlHeader(String[] metaKeywords,
			boolean windowTitleScript, String customJavaScript) {
		println(DTD_XHTML1_TRANS);
		printTimeStamp();
		println(HTML_XML);
		println(open("head"));
		if (CONF.windowtitle.length() > 0)
			windowTitle += " (" + CONF.windowtitle + ")";
		println(open("title") + windowTitle + close("title"));
		printStylesheetLink();
		// "(build "+ConfigurationImpl.BUILD_DATE+")"
		if (CONF.charset.length() > 0) {
			TagBuilder meta = new TagBuilder("meta");
			meta.add("http-equiv", "Content-Type");
			meta.add("content", "text/html");
			meta.add("charset", CONF.charset);
			println(meta.getEmpty());
		} else
			println(META_UTF8);
		if (metaKeywords != null) {
			for (int i = 0; i < metaKeywords.length; i++) {
				println(empty("meta name=\"keywords\" content=\""
						+ metaKeywords[i] + "\""));
			}
		}
		
		if (windowTitleScript || customJavaScript != null) {
			print(scriptOpenJavascript());
			if (windowTitleScript && windowTitle != null
					&& windowTitle.length() > 0) {
				print("parent.document.title='" + windowTitle + "';");
			}
			if (customJavaScript != null)
				print(customJavaScript);
			println(close("script"));
		}
		println(close("head"));
		if (pageType == null)
			println(open("body"));
		else {
			println("<body id=\"" + pageType + "\">");
			if (pageType != PageType.ALL_PACKAGES
					&& pageType != PageType.PACKAGE_FRAME
					&& pageType != PageType.ALL_CLASSES) {
				printBannerSection();
				// Used rather than #Content border-top to
				// avoid mis-styling frame sidebar pages.
				println("<div class=\"hr\" />");
			}
		}
		println(openDivWithID("Content"));
		println(getSectionDelimiter("START CONTENT"));
	}
	
	/**
	 * Prints an HTML comment timestamp; used at the top of Javadoc files.
	 */
	protected final void printTimeStamp() {
		if (!CONF.notimestamp) {
			println("<!-- Generated by javadoc: " + today() + " -->");
			println("<!-- XHTML Doclet formatting: http://xhtmldoclet.sf.net -->");
		}
	}
	
	/**
	 * Prints a &lt;link&gt; tag to "stylesheet.css" at the document root.
	 */
	protected final void printStylesheetLink() {
		String filename = pathToDocRoot + "stylesheet.css";
		println(new TagBuilder("link").add("rel", "stylesheet").add("type",
				"text/css").add("media", "all").add("href", filename).getEmpty());
	}
	
	/**
	 * Prints comment and &lt;/div&gt; to close #Content, custom footer if
	 * specified, &lt;/body&gt;, and &lt;/html&gt;.
	 */
	protected final void printXhtmlFooter() {
		println(getSectionDelimiter("END CONTENT"));
		println(close("div"));
		printCustomFooter();
		println(close("body"));
		println(close("html"));
	}
	
	/**
	 * Prints the #Banner section for pages that appear in the main frame. This
	 * includes a custom header (if specified), navigation bar with items
	 * specified by javadoc/doclet flags, and
	 * 
	 * @see #navLinkOverview()
	 * @see #navLinkPackage()
	 * @see #navLinkClass()
	 * @see #navLinkUse()
	 * @see #navLinkTree()
	 * @see #navLinkDeprecated()
	 * @see #navLinkIndex()
	 * @see #navLinkHelp()
	 * 
	 * @see #navWithinPage()
	 * @see #navDisplayOptions()
	 * @see #navPreviousNext()
	 */
	protected final void printBannerSection() {
		println(openDivWithID("Banner"));
		printCustomHeader();
		
		// Print navigation section
		println(openDivWithID("Navigation"));
		println(getSectionDelimiter("START NAVIGATION"));
		
		// Section headings can access pageType to decide whether to print a
		// link or not
		println(new TagBuilder("ul").add("id", "JavadocSections").getOpen());
		if (CONF.createoverview)
			navLinkOverview();
		navLinkPackage();
		navLinkClass();
		if (CONF.use)
			navLinkUse();
		if (CONF.createtree && CONF.root != null
				&& CONF.root.specifiedPackages() != null)
			navLinkTree();
		if (!CONF.nodeprecated && !CONF.nodeprecatedlist)
			navLinkDeprecated();
		if (CONF.createindex)
			navLinkIndex();
		if (!CONF.nohelp)
			navLinkHelp();
		println(close("ul") + getComment("#JavadocSections"));
		
		println(new TagBuilder("table").add("id", "PageRelativeLinks").getOpen());
		println(open("tr"));
		println(open("td"));
		// Print "Frames | No Frames | All Classes" links on all pages
		navDisplayOptions();
		// Print document-relative links to previous & next elements
		navPreviousNext();
		println(close("td"));
		// Print document-interal links for pages that require them
		navWithinPage(); // prints its own <td> tags as needed
		println(close("tr"));
		println(close("table"));
		
		println(getSectionDelimiter("END NAVIGATION"));
		println(close("div") + getComment("#Navigation"));
		
		println(close("div") + getComment("#Banner"));
	}
	
	/**
	 * UNIMPLEMENTED: Print header specified by the user.
	 */
	protected final void printCustomHeader() {
		// printUserHeaderFooter(header);
	}
	
	/**
	 * UNIMPLEMENTED: Print footer specified by the user.
	 */
	protected final void printCustomFooter() {
		
	}
	
	/**
	 * Print headers for identifying an entity, with hooks for CSS. Used in
	 * class and package pages to print the header describing the {@link Doc}.
	 * 
	 * @param entity The {@link Doc} element to include in the header.
	 * @param caption The properties caption to prefix the entity name with.
	 * 
	 * @see net.sf.odinms.exttools.doclet.pages.PackageSummary
	 * @see net.sf.odinms.exttools.doclet.pages.PackageTree
	 * @see net.sf.odinms.exttools.doclet.pages.PackageUse
	 */
	protected final void printEntityCaption(Doc entity, String caption) {
		String deprecated = "";
		if (entity instanceof ClassDoc && Util.isDeprecated((ClassDoc) entity))
			deprecated =
					" <em>("
							+ linkToLabelHref(CONF.propertyText("Deprecated"),
									"#Deprecated") + ")</em>";
		println(open("h1 id=\"entityName\"") + caption + " <span>"
				+ entity.name() + typeParameters(entity) + "</span>"
				+ deprecated + close("h1"));
		
	}
	
	/**
	 * Print headers for identifying an entity, with hooks for CSS. Used in
	 * class pages to print 2-tiered headings at the top of the page.
	 * 
	 * @param entity The {@link Doc} element to include in the header.
	 * @param caption The properties caption to prefix the entity name with.
	 * @param packageName The package name to print in the second heading.
	 * 
	 * @see net.sf.odinms.exttools.doclet.pages.ClassSummary
	 * @see net.sf.odinms.exttools.doclet.pages.ClassUse
	 */
	protected final void printEntityCaptionPackage(Doc entity, String caption,
			String packageName) {
		
		printEntityCaption(entity, caption);
		
		if (packageName != null && !packageName.equals(""))
			println(open("h2 id=\"entityPackage\"") + "in package <span>"
					+ packageName + "</span>" + close("h2"));
	}
	
	/**
	 * Return the path to the class page for a ClassDoc.
	 * 
	 * @param cd Class to which the path is requested.
	 * @param filename Name of the file (doesn't include path).
	 * @return The relative path to the summary page for the specified class.
	 */
	// TODO See if this is new or old; remove if deprecated method.
	protected final String pathToClass(ClassDoc cd, String filename) {
		return pathToPackageFile(cd.containingPackage(), filename);
	}
	
	/**
	 * Default behavior is to link to Overview page.
	 * 
	 * @see net.sf.odinms.exttools.doclet.pages.OverviewSummary#navLinkOverview()
	 */
	protected void navLinkOverview() {
		println(listItem(linkToLabelHrefTitle(OVERVIEW, pathToDocRoot
				+ "overview-summary" + CONF.ext, OVERVIEW_TIP)));
	}
	
	/**
	 * Default behavior is that Package is not available.
	 * 
	 * @see net.sf.odinms.exttools.doclet.pages.ClassSummary#navLinkPackage()
	 * @see net.sf.odinms.exttools.doclet.pages.ClassUse#navLinkPackage()
	 * @see net.sf.odinms.exttools.doclet.pages.PackageSummary#navLinkPackage()
	 * @see net.sf.odinms.exttools.doclet.pages.PackageTree#navLinkPackage()
	 * @see net.sf.odinms.exttools.doclet.pages.PackageUse#navLinkPackage()
	 */
	protected void navLinkPackage() {
		if (CONF.packages != null && CONF.packages.length == 1)
			println(listItem(linkToPackage(CONF.packages[0], PACKAGE)));
		else
			println(listItem(PACKAGE));
	}
	
	/**
	 * Default behavior is that Class is not available; override in Package.
	 * 
	 * @see net.sf.odinms.exttools.doclet.pages.ClassSummary#navLinkClass()
	 * @see net.sf.odinms.exttools.doclet.pages.ClassUse#navLinkClass()
	 */
	protected void navLinkClass() {
		println(listItem(CLASS));
	}
	
	/**
	 * Default behavior is that Use is not available; override in class use.
	 * 
	 * @see net.sf.odinms.exttools.doclet.pages.ClassSummary#navLinkUse()
	 * @see net.sf.odinms.exttools.doclet.pages.ClassUse#navLinkUse()
	 * @see net.sf.odinms.exttools.doclet.pages.PackageSummary#navLinkUse()
	 * @see net.sf.odinms.exttools.doclet.pages.PackageTree#navLinkUse()
	 * @see net.sf.odinms.exttools.doclet.pages.PackageUse#navLinkUse()
	 */
	protected void navLinkUse() {
		println(listItem(USE));
	}
	
	/**
	 * Link to the hierarchy tree for the current package context.
	 * 
	 * @see net.sf.odinms.exttools.doclet.pages.OverviewTree#navLinkTree()
	 * @see net.sf.odinms.exttools.doclet.pages.PackageTree#navLinkTree()
	 */
	protected void navLinkTree() {
		println(listItem(linkToLabelHrefTitle(TREE, pathToDocRoot
				+ "overview-tree" + CONF.ext,
				CONF.propertyText("navtitle.Tree.overview"))));
	}
	
	/**
	 * Link to the list of deprecated symbols if generated.
	 * 
	 * @see net.sf.odinms.exttools.doclet.pages.DeprecatedList#navLinkDeprecated()
	 */
	protected void navLinkDeprecated() {
		println(listItem(linkToLabelHrefTitle(DEPRECATED, pathToDocRoot
				+ "deprecated-list" + CONF.ext, DEPRECATED_TIP)));
	}
	
	/**
	 * Link to the index of documented symbols.
	 * 
	 * @see net.sf.odinms.exttools.doclet.pages.AbstractIndex#navLinkIndex()
	 */
	protected void navLinkIndex() {
		println(listItem(linkToLabelHrefTitle(INDEX, pathToDocRoot
				+ CONF.rootIndexPage, INDEX_TIP)));
	}
	
	/**
	 * Link to the help page if generated.
	 * 
	 * @see net.sf.odinms.exttools.doclet.pages.Help#navLinkHelp()
	 */
	protected void navLinkHelp() {
		String helpFilename = "help-doc" + CONF.ext;
		println(listItem(linkToLabelHrefTitle(HELP, pathToDocRoot
				+ helpFilename, HELP_TIP)));
	}
	
	/**
	 * Print "Frames | No Frames | All Classes" links in all pages with a banner
	 * section. Includes code for exiting or entering frames and JavaScript.
	 */
	protected final void navDisplayOptions() {
		// TODO Refactor to use pathBetweenPackages instead of pathAfterDocRoot.
		String framesLink =
				linkToLabelHrefTarget(FRAMES, pathToDocRoot + "index"
						+ CONF.ext + "?" + pathAfterDocRoot(path) + filename,
						"_top");
		String noFramesLink = linkToLabelHrefTarget(NOFRAMES, filename, "_top");
		String allClassesLink =
				linkToLabelHref(ALLCLASSES, pathToDocRoot + "allclasses-noframe"
						+ CONF.ext);
		
		// Always print the "Frames" link, so if a page appears within some
		// other frame, it can always link back to its parent frame
		println(scriptOpenJavascript() + "\n<!--");
		println("document.write('" + openULWithID("DisplayOptions") + "')");
		println("if (window==top) {");
		println("  document.writeln('" + listItem(framesLink) + "');");
		println("  document.writeln('" + listItem(NOFRAMES) + "');");
		println("  document.writeln('" + listItemLast(allClassesLink) + "');");
		println("} else {");
		println("  document.writeln('" + listItem(FRAMES) + "');");
		println("  document.writeln('" + listItemLast(noFramesLink) + "');");
		println("}\ndocument.write('" + close("ul") + "');\n//-->");
		println(close("script"));
		// If JavaScript is not enabled, print all 3 links as best we can.
		println(open("noscript"));
		println(openULWithID("DisplayOptions"));
		println(listItem(framesLink));
		println(listItem(noFramesLink));
		println(listItemLast(allClassesLink));
		println(close("ul"));
		println(close("noscript"));
	}
	
	/**
	 * No action by default; subclasses must provide document section links.
	 * 
	 * @see net.sf.odinms.exttools.doclet.pages.AbstractIndex#navWithinPage()
	 * @see net.sf.odinms.exttools.doclet.pages.AbstractTree#navWithinPage()
	 * @see net.sf.odinms.exttools.doclet.pages.ClassSummary#navWithinPage()
	 * @see net.sf.odinms.exttools.doclet.pages.DeprecatedList#navWithinPage()
	 * @see net.sf.odinms.exttools.doclet.pages.PackageSummary#navWithinPage()
	 */
	protected void navWithinPage() {
	}
	
	/**
	 * No action by default; subclasses must provide Previous / Next links.
	 * 
	 * @see net.sf.odinms.exttools.doclet.pages.ClassSummary#navPreviousNext()
	 * @see net.sf.odinms.exttools.doclet.pages.ClassUse#navPreviousNext()
	 * @see net.sf.odinms.exttools.doclet.pages.PackageSummary#navPreviousNext()
	 * @see net.sf.odinms.exttools.doclet.pages.PackageTree#navPreviousNext()
	 * @see net.sf.odinms.exttools.doclet.pages.PackageUse#navPreviousNext()
	 * @see net.sf.odinms.exttools.doclet.pages.SplitIndex#navPreviousNext()
	 */
	protected void navPreviousNext() {
	}
	
	// FIXME Fix links within comments
	// Has to do with redirectRelativeLinks()?
	protected String getCommentTagsString(Doc doc, Tag[] tags, boolean depr,
			boolean first) {
		if (CONF.nocomment)
			return "";
		String commentTags = commentTagsToString(null, doc, tags, first);
		if (depr)
			return open("span class=\"deprecated\"") + commentTags
					+ close("span");
		else
			return commentTags;
	}
	
	protected String inlineComment(Doc doc) {
		return getCommentTagsString(doc, doc.inlineTags(), false, false);
	}
	
	// TODO Refactor mess from HtmlDocletWriter
	/**
	 * Converts inline tags and text to text strings, expanding the inline tags
	 * along the way. Called wherever text can contain an inline tag, such as in
	 * comments or in free-form text arguments to non-inline tags.
	 * 
	 * @param holderTag specific tag where comment resides
	 * @param doc specific doc where comment resides
	 * @param tags array of text tags and inline tags (often alternating)
	 *        present in the text of interest for this doc
	 * @param isFirstSentence true if text is first sentence
	 */
	protected String commentTagsToString(Tag holderTag, Doc doc, Tag[] tags,
			boolean isFirstSentence) {
		StringBuffer result = new StringBuffer();
		// Array of all possible inline tags for this javadoc run
		CONF.tagletManager.checkTags(doc, tags, true);
		for (int i = 0; i < tags.length; i++) {
			Tag tagElement = tags[i];
			String tagName = tagElement.name();
			if (tagElement instanceof SeeTag) {
				result.append(seeTagToString((SeeTag) tagElement));
			} else if (!tagName.equals("Text")) {
				// TagletOutput output = TagletWriter.getInlineTagOuput(
				// CONF.tagletManager, holderTag, (Tag) tagelem,
				// getTagletWriterInstance(isFirstSentence));
				// result.append(output == null ? "" : output.toString());
				continue;
			} else {
				// This is just a regular text tag. The text may contain html
				// links (<a>)
				// or inline tag {@docRoot}, which will be handled as special
				// cases.
				String text =
						redirectRelativeLinks(tagElement.holder(),
								tagElement.text());
				// Replace @docRoot only if not represented by an instance of
				// DocRootTaglet,
				// that is, only if it was not present in a source file doc
				// comment.
				// This happens when inserted by the doclet (a few lines
				// above in this method). [It might also happen when passed in
				// on the command
				// line as a text argument to an option (like -header).]
				text = replaceDocRootDir(text);
				if (isFirstSentence) {
					text = stripBlockLevelTags(text);
				}
				StringTokenizer lines = new StringTokenizer(text, "\r\n", true);
				StringBuffer textBuff = new StringBuffer();
				while (lines.hasMoreTokens()) {
					StringBuffer line = new StringBuffer(lines.nextToken());
					Util.replaceTabs(CONF.sourcetab, line);
					textBuff.append(line.toString());
				}
				result.append(textBuff);
			}
		}
		return result.toString();
	}
	
	/**
	 * Convert a <code>&#64;see</code> tag to a string.
	 * 
	 * @param see The {@link SeeTag} to convert to a string.
	 * @return A string representing a <code>&#64;see</code> tag.
	 */
	protected String seeTagToString(SeeTag see) {
		String tagName = see.name();
		if (!(tagName.startsWith("@link") || tagName.equals("@see"))) {
			return "";
		}
		StringBuffer result = new StringBuffer();
		boolean isplaintext = tagName.toLowerCase().equals("@linkplain");
		String label = see.label();
		label =
				(label.length() > 0) ? ((isplaintext) ? label : open("code")
						+ label + close("code")) : "";
		String seetext = replaceDocRootDir(see.text());
		
		// Check if @see is an href or "string"
		if (seetext.startsWith("<") || seetext.startsWith("\"")) {
			result.append(seetext);
			return result.toString();
		}
		
		// The text from the @see tag. We will output this text when a label is
		// not specified.
		String text =
				(isplaintext) ? seetext : open("code") + seetext
						+ close("code");
		
		ClassDoc refClass = see.referencedClass();
		String refClassName = see.referencedClassName();
		MemberDoc refMember = see.referencedMember();
		String refMemberName = see.referencedMemberName();
		
		if (refClass == null) {
			// @see is not referencing an included class
			PackageDoc refPackage = see.referencedPackage();
			if (refPackage != null && refPackage.isIncluded()) {
				// @see is referencing an included package
				String packageName =
						isplaintext ? refPackage.name() : open("code")
								+ refPackage.name() + close("code");
				result.append(getPackageLink(refPackage, label.length() == 0
						? packageName
						: label, false));
			} else {
				// @see is not referencing an included class or package. Check
				// for cross links.
				String classCrossLink =
						getCrossClassLink(refClassName, refMemberName, label,
								false, "", !isplaintext);
				String packageCrossLink = getCrossPackageLink(refClassName);
				if (packageCrossLink != null) {
					// result.append(getHyperLink(packageCrossLink, "", (label
					// .length() == 0) ? text : label, false));
					result.append(linkToLabelHref((label.length() == 0)
							? text
							: label, packageCrossLink));
				} else if (classCrossLink != null) {
					// Class cross link found (possibly to a member in the
					// class)
					result.append(classCrossLink);
				} else {
					// No cross link found so print warning
					// CONF.getDocletSpecificMsg().warning(see.position(),
					// "doclet.see.class_or_package_not_found",tagName,
					// seetext);
					
					result.append((label.length() == 0) ? text : label);
				}
			}
		} else if (refMemberName == null) {
			// Must be a class reference since refClass is not null and
			// refMemName is null.
			if (label.length() == 0) {
				label =
						(isplaintext) ? refClass.name() : open("code")
								+ refClass.name() + close("code");
			}
			result.append(linkToLabelHref(label, refClass.name()));
			// result.append(getLink(new LinkInfoImpl(refClass, label)));
			
		} else if (refMember == null) {
			// Must be a member reference since refClass is not null and
			// refMemName is not null.
			// However, refMem is null, so this referenced member does not
			// exist.
			result.append((label.length() == 0) ? text : label);
		} else {
			// Must be a member reference since refClass is not null and
			// refMemName is not null.
			// refMem is not null, so this @see tag must be referencing a valid
			// member.
			ClassDoc containing = refMember.containingClass();
			if (see.text().trim().startsWith("#")
					&& !(containing.isPublic() || Util.isLinkable(containing,
							CONF))) {
				// Since the link is relative and the holder is not even being
				// documented, this must be an inherited link. Redirect it.
				// The current class either overrides the referenced member or
				// inherits it automatically.
				// containing = ((ClassWriterImpl) this).getClassDoc();
			}
			if (CONF.currentClass != containing) {
				refMemberName = containing.name() + "." + refMemberName;
			}
			if (refMember instanceof ExecutableMemberDoc) {
				if (refMemberName.indexOf('(') < 0) {
					refMemberName +=
							((ExecutableMemberDoc) refMember).signature();
				}
			}
			text =
					(isplaintext) ? refMemberName : open("code")
							+ refMemberName + close("code");
			
			result.append(getDocLink(containing, refMember,
					(label.length() == 0) ? text : label));
		}
		return result.toString();
	}
	
	/**
	 * Remove block-level HTML tags in a string of text.
	 * 
	 * @param text The text in which to replace tags.
	 * @return A modified version of the parameter without block-level tags.
	 */
	protected String stripBlockLevelTags(String text) {
		if (text.indexOf('<') < 0) {
			return text;
		}
		String[] blockTags =
				{"<ul>", "</ul>", "<ol>", "</ol>", "<dl>", "</dl>", "<table>",
						"</table>", "<tr>", "</tr>", "<td>", "</td>", "<th>",
						"</th>", "<p>", "</p>", "<li>", "</li>", "<dd>",
						"</dd>", "<dir>", "</dir>", "<dt>", "</dt>", "<h1>",
						"</h1>", "<h2>", "</h2>", "<h3>", "</h3>", "<h4>",
						"</h4>", "<h5>", "</h5>", "<h6>", "</h6>", "<pre>",
						"</pre>", "<menu>", "</menu>", "<listing>",
						"</listing>", "<hr>", "<blockquote>", "</blockquote>",
						"<center>", "</center>"};
		for (int tag = 0; tag < blockTags.length; tag++)
			text = replace(text, blockTags[tag], "");
		return text;
	}
	
	/**
	 * Replace all instances of text in a given string.
	 * 
	 * @param subject The string to search for the old text.
	 * @param oldText The string to be replaced.
	 * @param newText The string to be substituted.
	 * @return The subject string, with <code>oldText</code> replaced by
	 *         <code>newText</code>.
	 */
	protected String replace(String subject, String oldText, String newText) {
		while (true) {
			int startindex = subject.indexOf(oldText);
			if (startindex < 0) {
				return subject;
			}
			int endindex = startindex + oldText.length();
			StringBuffer replaced = new StringBuffer();
			if (startindex > 0) {
				replaced.append(subject.substring(0, startindex));
			}
			replaced.append(newText);
			if (subject.length() > endindex) {
				replaced.append(subject.substring(endindex));
			}
			subject = replaced.toString();
		}
	}
	
	/**
	 * Suppose a piece of documentation has a relative link. When you copy that
	 * documentation to another place such as the index or class-use page, that
	 * relative link will no longer work. We should redirect those links so that
	 * they will work again.
	 * <p>
	 * Here is the algorithm used to fix the link:
	 * <p>
	 * &lt;relative link&gt; => docRoot + &lt;relative path to file&gt; +
	 * &lt;relative link&gt;
	 * <p>
	 * For example, suppose com.sun.javadoc.RootDoc has this link: &lt;a
	 * href="package-summary.html"&gt;The package Page&lt;/a&gt;
	 * <p>
	 * If this link appeared in the index, we would redirect the link like this:
	 * 
	 * &lt;a href="./com/sun/javadoc/package-summary.html"&gt;The package
	 * Page&lt;/a&gt;
	 * 
	 * @param doc the Doc object whose documentation is being written.
	 * @param text the text being written.
	 * 
	 * @return the text, with all the relative links redirected to work.
	 * 
	 * Was private, only protected for documentation
	 */
	// TODO Refactor mess from HtmlDocletWriter (use hrefToDoc)
	// TODO Handle documenting local classes, even when external link is given
	// to Javadoc
	protected String redirectRelativeLinks(Doc doc, String text) {
		if (doc == null || shouldNotRedirectRelativeLinks()) {
			return text;
		}
		
		String redirectPathFromRoot;
		try {
			redirectPathFromRoot = hrefToDoc(doc);
		}
		catch (DocletAbortException exc) {
			return text;
		}
		
		if (!redirectPathFromRoot.endsWith(DirectoryManager.URL_FILE_SEPERATOR)) {
			redirectPathFromRoot += DirectoryManager.URL_FILE_SEPERATOR;
		}
		
		// Redirect all relative links.
		int begin = text.toLowerCase().indexOf("<a");
		int end;
		if (begin >= 0) {
			StringBuffer textBuff = new StringBuffer(text);
			// find each <a> tag and process href attribute
			while (begin >= 0) {
				begin = textBuff.indexOf("=", begin) + 1;
				end = textBuff.indexOf(">", begin + 1);
				if (begin == 0) {
					// Link has no equal symbol.
					CONF.getDocletSpecificMsg().warning(doc.position(),
							"doclet.malformed_html_link_tag", text);
					break;
				}
				if (end == -1) {
					// Break without warning. This <a> tag is not necessarily
					// malformed. The text might be missing '>' character
					// because the href has an inline tag.
					break;
				}
				if (textBuff.substring(begin, end).indexOf("\"") != -1) {
					begin = textBuff.indexOf("\"", begin) + 1;
					end = textBuff.indexOf("\"", begin + 1);
					if (begin == 0 || end == -1) {
						// Link is missing a quote.
						break;
					}
				}
				String relativeLink = textBuff.substring(begin, end);
				// System.out.println(relativeLink);
				// System.out.println(pathAfterDocRoot(path));
				if (!(relativeLink.toLowerCase().startsWith("mailto:")
						|| relativeLink.toLowerCase().startsWith("http:")
						|| relativeLink.toLowerCase().startsWith("https:") || relativeLink.toLowerCase().startsWith(
						"file:"))) {
					relativeLink =
							"{@" + (new DocRootTaglet()).getName() + "}"
									+ redirectPathFromRoot + relativeLink;
					textBuff.replace(begin, end, relativeLink);
				}
				// System.out.println(relativeLink);
				// System.out.println();
				begin =
						textBuff.toString().toLowerCase().indexOf("<a",
								begin + 1);
			}
			return textBuff.toString();
		}
		return text;
	}
	
	// TODO Refactor mess from HtmlDocletWriter
	/**
	 * Return true if relative links should not be redirected.
	 * 
	 * @return Return true if a relative link should not be redirected.
	 * 
	 * @deprecated The conditions will never be satisfied in XHTML Doclet;
	 *             refactor functionality for replacement classes.
	 */
	private boolean shouldNotRedirectRelativeLinks() {
		return (this instanceof AnnotationTypeWriter
				|| this instanceof ClassWriter || this instanceof PackageSummaryWriter);
	}
	
	// TODO Refactor mess from HtmlDocletWriter
	/**
	 * Replace {&#064;docRoot} tag used in options that accept HTML text, such
	 * as -header, -footer and -bottom, and when converting a relative HREF
	 * where commentTagsToString inserts a {&#064;docRoot} where one was
	 * missing. (Also see DocRootTaglet for {&#064;docRoot} tags in doc
	 * comments.)
	 * <p>
	 * Replace {&#064;docRoot} tag in htmlstr with the relative path to the
	 * destination directory from the directory where the file is being written,
	 * looping to handle all such tags in htmlstr.
	 * <p>
	 * For example, for "-d docs" and -header containing {&#064;docRoot}, when
	 * the HTML page for source file p/C1.java is being generated, the
	 * {&#064;docRoot} tag would be inserted into the header as "../", the
	 * relative path from docs/p/ to docs/ (the document root).
	 * <p>
	 * Note: This doc comment was written with '&amp;#064;' representing '@' to
	 * prevent the inline tag from being interpreted.
	 * 
	 * @param htmlString A string in which to replace occurrences of {@docroot}.
	 * @return A string where {@docroot} has been replaced by the document root.
	 * 
	 * @deprecated
	 */
	protected String replaceDocRootDir(String htmlString) {
		// Return if no inline tags exist
		int index = htmlString.indexOf("{@");
		if (index < 0) {
			return htmlString;
		}
		String lowerHtml = htmlString.toLowerCase();
		// Return index of first occurrence of {@docroot}
		// Note: {@docRoot} is not case sensitive when passed in w/command line
		// option
		index = lowerHtml.indexOf("{@docroot}", index);
		if (index < 0) {
			return htmlString;
		}
		StringBuffer buf = new StringBuffer();
		int previndex = 0;
		while (true) {
			// Search for lowercase version of {@docRoot}
			index = lowerHtml.indexOf("{@docroot}", previndex);
			// If next {@docRoot} tag not found, append rest of htmlstr and exit
			// loop
			if (index < 0) {
				buf.append(htmlString.substring(previndex));
				break;
			}
			// If next {@docroot} tag found, append htmlstr up to start of tag
			buf.append(htmlString.substring(previndex, index));
			previndex = index + 10; // length for {@docroot} string
			// Insert relative path where {@docRoot} was located
			buf.append(docRootPathNoSlash);
			// Append slash if next character is not a slash
			if (docRootPathNoSlash.length() > 0
					&& previndex < htmlString.length()
					&& htmlString.charAt(previndex) != '/') {
				buf.append(DirectoryManager.URL_FILE_SEPERATOR);
			}
		}
		return buf.toString();
	}
	
	/**
	 * Create an HTML link to an executable member by its signature. Accepts
	 * both {@link ConstructorDoc} and {@link MethodDoc} objects.
	 * 
	 * @param target The constructor or method to link to.
	 * @return A link to the specified target.
	 */
	protected String signatureLink(ExecutableMemberDoc target) {
		return linkToLabelHref(target.name(), hrefToDoc(target));
	}
	
	protected String linkToType(MemberDoc doc) {
		return getTypeString(doc, true, false);
	}
	
	/***************************************************************************
	 * Return a class cross link to external class documentation. The name must
	 * be fully qualified to determine which package the class is in. The -link
	 * option does not allow users to link to external classes in the "default"
	 * package.
	 * 
	 * @param qualifiedClassName The qualified name of the external class.
	 * @param refMemName The name of the member being referenced. This should be
	 *        null or empty string if no member is being referenced.
	 * @param label The label for the external link.
	 * @param bold True if the link should be bold.
	 * @param style The style of the link.
	 * @param code True if the label should be code font.
	 * 
	 * @return A link to external class documentation.
	 * @deprecated
	 */
	// TODO Refactor to use hrefToDoc or pathBetweenPackages. (only one use)
	protected String getCrossClassLink(String qualifiedClassName,
			String refMemName, String label, boolean bold, String style,
			boolean code) {
		String className = "", packageName =
				qualifiedClassName == null ? "" : qualifiedClassName;
		int periodIndex;
		while ((periodIndex = packageName.lastIndexOf('.')) != -1) {
			className =
					packageName.substring(periodIndex + 1, packageName.length())
							+ (className.length() > 0 ? "." + className : "");
			// String defaultLabel = code ? open("code") + className
			// + close("code") : className;
			packageName = packageName.substring(0, periodIndex);
			if (getCrossPackageLink(packageName) != null) {
				// The package exists in external documentation, so link to the
				// external class (assuming that it exists). This is definitely
				// a limitation of the -link option. There are ways to determine
				// if an external package exists, but no way to determine if the
				// external class exists. We just have to assume that it does.
				return null;
				/*
				 * return getHyperLink(CONF.extern.getExternalLink(packageName,
				 * relativePath, className + CONF.ext), refMemName == null ? "" :
				 * refMemName, label == null || label.length() == 0 ?
				 * defaultLabel : label, bold, style, CONF.propertyText(
				 * "doclet.Href_Title_Class_Or_Interface", packageName), "");
				 */
			}
		}
		return null;
	}
	
	/**
	 * @return A link to external package documentation.
	 * @deprecated
	 */
	protected String getCrossPackageLink(String pkgName) {
		return CONF.extern.getExternalLink(pkgName, pathToDocRoot,
				"package-summary" + CONF.ext);
	}
	
	/**
	 * Return the link for the given member.
	 * 
	 * @param context The id of the context where the link will be printed.
	 * @param doc The member being linked to.
	 * @param label The label for the link.
	 * @param bold True if the link should be bold.
	 * @return The link for the given member.
	 * 
	 * @deprecated
	 */
	protected String getDocLink(int context, MemberDoc doc, String label,
			boolean bold) {
		return getDocLink(doc.containingClass(), doc, label);
	}
	
	/**
	 * Return the link for the given member.
	 * 
	 * @param classDoc the classDoc that we should link to. This is not
	 *        necessarily equal to doc.containingClass(). We may be inheriting
	 *        comments.
	 * @param doc the member being linked to.
	 * @param label the label for the link.
	 * 
	 * @return the link for the given member.
	 * 
	 * @deprecated
	 */
	protected String getDocLink(ClassDoc classDoc, MemberDoc doc, String label) {
		if (!(doc.isIncluded() || Util.isLinkable(classDoc, CONF))) {
			return label;
		} else if (doc instanceof ExecutableMemberDoc) {
			ExecutableMemberDoc emd = (ExecutableMemberDoc) doc;
			return linkToLabelHref(label, classDoc.name() + memberAnchor(emd));
			// return getLink(new LinkInfoImpl(context, classDoc,
			// getAnchor(emd), label));
		} else if (doc instanceof MemberDoc) {
			return linkToLabelHref(label, classDoc.name());
			// return getLink(new LinkInfoImpl(context, classDoc, doc.name(),
			// label));
		} else {
			return label;
		}
	}
	
	/**
	 * Create an IMG tag with a specified source and alt tag text. If the alt
	 * text is blank, a single space is used to create and XHTML-valid tag.
	 * 
	 * @param src The href to the image source.
	 * @param alt The text to use for the alt tag.
	 * @return A tag of the form: <code>&lt;img src="src" alt="alt" /&gt;</code>
	 */
	protected final String imgSrcAlt(String src, String alt) {
		TagBuilder img = new TagBuilder("img");
		img.add("src", src);
		img.add("alt", (alt.equals("") ? " " : alt));
		return img.getEmpty();
	}
	
	/**
	 * Wrap the parameter in &lt;li&gt; tags with id"current". Used for marking
	 * the current page in the navigation bar.
	 * 
	 * @param innerText The text to appear inside a list item.
	 * @return "&lt;li id="current"&gt;innerText&lt;/li&gt;"
	 */
	protected final String listItemCurrent(String innerText) {
		return "<li id=\"current\">" + innerText + "</li>";
	}
	
	/**
	 * Wrap the parameter in &lt;li&gt; tags with class="label".
	 * 
	 * @param innerText The text to appear inside a list item.
	 * @return "&lt;li class="label"&gt;innerText&lt;/li&gt;"
	 */
	protected final String listItemLabel(String innerText) {
		return "<li class=\"label\">" + innerText + "</li>";
	}
	
	/**
	 * Wrap the parameter in &lt;li&gt; tags with class="last". Used for
	 * designating as the last item in a list. (CSS 2.1 can do it with the
	 * <code>:last-child</code> pseudo-element, but is not widely supported.)
	 * 
	 * @param innerText The text to appear inside a list item.
	 * @return "&lt;li class="last"&gt;innerText&lt;/li&gt;"
	 */
	protected final String listItemLast(String innerText) {
		return "<li class=\"last\">" + innerText + "</li>";
	}
	
	protected final void printUnorderedListWithLast(List items) {
		Iterator itemList = items.iterator();
		Object obj;
		print(open("ul"));
		while (itemList.hasNext()) {
			obj = itemList.next();
			print(itemList.hasNext()
					? listItem(obj.toString())
					: listItemLast(obj.toString()));
		}
		println(close("ul"));
	}
	
	// TODO Modify to return distinct titles for Exception and Enum
	// TODO Check whether Enum and Error are switched
	/**
	 * @deprecated
	 */
	protected final String linkPropertyForLabel(String entityType) {
		String linkTitle = "";
		if (entityType.equals("doclet.Interfaces"))
			linkTitle = "doclet.hrefTitleInterface";
		else if (entityType.equals("doclet.Classes")
				|| entityType.equals("doclet.Enums")
				|| entityType.equals("doclet.Exceptions"))
			linkTitle = "doclet.hrefTitleClass";
		else if (entityType.equals("doclet.Errors"))
			linkTitle = "doclet.hrefTitleError";
		else if (entityType.equals("doclet.AnnotationTypes"))
			linkTitle = "doclet.hrefTitleAnnotation";
		return linkTitle;
	}
	
	/** @deprecated */
	protected String linkToPackage(PackageDoc pkg) {
		return linkToPackage(pkg, pkg.name());
	}
	
	/**
	 * Return a link to the given package.
	 * 
	 * @param pkg The package to link to.
	 * @param label The text to appear within the anchor tags
	 * @return A link to the given package, or the label if pkg is not included.
	 * 
	 * @deprecated
	 */
	protected String linkToPackage(PackageDoc pkg, String label) {
		boolean included = (pkg != null && pkg.isIncluded());
		if (!included) {
			for (PackageDoc p : CONF.packages) {
				if (p.equals(pkg)) {
					included = true;
					break;
				}
			}
		}
		if (included || pkg == null) {
			return linkToLabelHref(label, pathToPackageFile(pkg,
					"package-summary" + CONF.ext));
		} else {
			String crossPackageLink =
					getCrossPackageLink(Util.getPackageName(pkg));
			if (crossPackageLink != null) {
				return linkToLabelHref(label, crossPackageLink);
			} else {
				return label;
			}
		}
	}
	
	/**
	 * Return the link to the given package.
	 * 
	 * @param pkg the package to link to.
	 * @param label the label for the link.
	 * @param isBold true if the label should be bold.
	 * @return the link to the given package.
	 * 
	 * @deprecated
	 */
	protected String getPackageLink(PackageDoc pkg, String label, boolean isBold) {
		return getPackageLink(pkg, label, isBold, "");
	}
	
	/**
	 * Return the link to the given package.
	 * 
	 * @param pkg the package to link to.
	 * @param label the label for the link.
	 * @param isBold true if the label should be bold.
	 * @param style the font of the package link label.
	 * @return the link to the given package.
	 * 
	 * @deprecated
	 */
	protected String getPackageLink(PackageDoc pkg, String label,
			boolean isBold, String style) {
		boolean included = pkg != null && pkg.isIncluded();
		if (!included) {
			PackageDoc[] packages = CONF.packages;
			for (int i = 0; i < packages.length; i++) {
				if (packages[i].equals(pkg)) {
					included = true;
					break;
				}
			}
		}
		if (included || pkg == null) {
			return linkToLabelHref(label, pathToPackageFile(pkg,
					"package-summary" + CONF.ext));
		} else {
			String crossPkgLink = getCrossPackageLink(Util.getPackageName(pkg));
			if (crossPkgLink != null) {
				// return getHyperLink(crossPkgLink, "", label, isBold, style);
				return linkToLabelHref(label, crossPkgLink);
			} else {
				return label;
			}
		}
	}
	
	/**
	 * Strips the base destination path from the current full path. This allows
	 * for shortening log statements to be relative to the generated javadoc
	 * document root. If the full path doesn't contain the configured base path,
	 * there is no effect.
	 * 
	 * @param fullPath The complete directory path of the current file.
	 * @return The directory path segment that is beyond the base path, or
	 *         <code>fullPath</code> if the base path does not appear.
	 * 
	 * @deprecated
	 */
	protected String pathAfterDocRoot(String fullPath) {
		String basePath = CONF.destDirName;
		return (fullPath.indexOf(basePath) == -1)
				? fullPath
				: fullPath.substring(fullPath.indexOf(basePath)
						+ basePath.length());
	}
	
	protected String typeParameters(Doc doc) {
		return typeParameters(doc, false);
	}
	
	protected String typeParameters(Doc doc, boolean includeLinks) {
		Type[] vars;
		if (doc instanceof ExecutableMemberDoc) {
			vars = ((ExecutableMemberDoc) doc).typeParameters();
		} else if (doc instanceof Type
				&& ((Type) doc).asParameterizedType() != null) {
			vars = ((Type) doc).asParameterizedType().typeArguments();
		} else if (doc instanceof ClassDoc) {
			vars = ((ClassDoc) doc).typeParameters();
		} else { // No type parameters to document
			return "";
		}
		
		if (vars != null && vars.length > 0)
			return "&lt;" + getJoinedStrings(vars, ",") + "&gt;";
		return "";
	}
	
	/**
	 * Find the visibility modifier for a given {@link ProgramElementDoc}.
	 * 
	 * @param doc The program element to examine for visibility modifier.
	 * @return A string of the declared or implied visibility modifier.
	 */
	protected final String visibilityModifier(ProgramElementDoc doc) {
		String modifiers = doc.modifiers();
		return (modifiers.contains("public"))
				? "public "
				: (modifiers.contains("protected"))
						? "protected "
						: (modifiers.contains("private"))
								? "private "
								: "default ";
	}
	
	/**
	 * Create the JavaScript command to change a parent frame's window title.
	 * 
	 * @return <code>"parent.document.title='windowTitle';"</code> OR<br />
	 *         <code>null</code> if the <code>windowTitle</code> is set to
	 *         null or an empty string.
	 * 
	 * @deprecated
	 */
	protected final String getWindowTitleJavascript() {
		if (windowTitle != null && windowTitle.length() > 0) {
			return "parent.document.title='" + windowTitle + "';";
		} else {
			return null;
		}
	}
	
	protected String getCommentSummary(Doc doc) {
		if (doc == null)
			return "";
		return getCommentTagsString(doc, doc.firstSentenceTags(), false, true);
	}
	
	/**
	 * Retrieve the modifiers from an element and create a formatted string.
	 * 
	 * @param doc The element from which to extract the modifiers.
	 * @return A space-separated String containing the desired modifiers.
	 */
	protected String modifiersString(ProgramElementDoc doc) {
		return modifiersString(doc, false);
	}
	
	/**
	 * Retrieve the modifiers from an element and create a formatted string.
	 * 
	 * @param doc The element from which to extract the modifiers.
	 * @param allModifiers Whether all modifiers should be displayed.
	 * @return A space-separated String containing the desired modifiers.
	 */
	protected String modifiersString(ProgramElementDoc doc, boolean allModifiers) {
		String modifier;
		List<String> modifiers = new Vector<String>();
		StringTokenizer tok = new StringTokenizer(doc.modifiers());
		while (tok.hasMoreTokens()) {
			modifier = tok.nextToken();
			if (allModifiers
					|| !(modifier.equals("public") || modifier.equals("final") || modifier.equals("interface")))
				modifiers.add(modifier);
		}
		modifiers.add(""); // adds trailing space only if there are 1+
		// modifiers
		return getJoinedStrings(modifiers, " ");
	}
	
	/**
	 * Retrieve the name of a type and create a formatted string.
	 * 
	 * @param doc The element from which to extract the type name.
	 * @param asLink Whether the type name should be embedded in a link tag.
	 * @param qualified Whether the reference should be fully qualified.
	 * @return A String with the type, perhaps within a link; null if
	 *         <code>doc</code> is not a {@link MethodDoc} or {@link FieldDoc}.
	 */
	// TODO Refactor link creation OUT of method--name is misleading
	protected String getTypeString(MemberDoc doc, boolean asLink,
			boolean qualified) {
		Type type;
		if (doc instanceof MethodDoc)
			type = ((MethodDoc) doc).returnType();
		else if (doc instanceof FieldDoc)
			type = ((FieldDoc) doc).type();
		else
			return null;
		
		String typeString;
		if (!asLink || type.isPrimitive())
			typeString = type.typeName();
		else
			typeString = linkToClass(type.asClassDoc()) + type.dimension();
		// type.asClassDoc()
		// type.asParameterizedType()
		// type.asTypeVariable()
		// type.asWildcardType()
		// type.dimension()
		// type.typeName() // unqualified, excludes dimension information
		// type.qualifiedTypeName() // excludes dimension information
		return typeString;
	}
	
	/**
	 * Create an HTML link to a given type class, or return primitive type name.
	 * 
	 * @param type The type to link to.
	 * @return An HTML link to the type's class, or the type name if primitive.
	 */
	protected String linkToType(Type type) {
		if (type instanceof ClassDoc)
			return linkToClass((ClassDoc) type, false);
		return type.typeName();
	}
	
	/**
	 * Create an HTML link to a given class, without fully qualified name.
	 * 
	 * @param target The class/interface to link to.
	 * @return A link to the specified class.
	 */
	protected String linkToClass(ClassDoc target) {
		return linkToClass(target, false);
	}
	
	/**
	 * Create an HTML link to a given class.
	 * 
	 * @param target The class/interface to link to.
	 * @param fullyQualified Whether the link label should be the
	 *        fully-qualified class name.
	 * @return A link to the specified class, with fully-qualified name.
	 */
	protected String linkToClass(ClassDoc target, boolean fullyQualified) {
		if (CONF.classList.contains(target) || CONF.extern.isExternal(target)) {
			String label =
					(target.isAnnotationType())
							? "@" + target.name()
							: (fullyQualified)
									? target.qualifiedName()
									: target.name();
			return "<a href=\"" + hrefToDoc(target) + "\">" + label + "</a>";
		} else {
			return target.qualifiedName();
		}
	}
	
	/**
	 * Return path to the given file in the given package. If the filename is
	 * "Object.html", the package is "java.lang", and the relative path is
	 * "../.." then the result will be "../../java/lang/Object.html"
	 * 
	 * @param pd Package in which the file name is assumed to be.
	 * @param filename File name to which path string belongs.
	 * @return The relative path to a file within a given package.
	 */
	protected final String pathToPackageFile(PackageDoc pd, String filename) {
		// TODO Refactor to use pathBetweenPackages(pd) for base path.
		return pathToDocRoot + DirectoryManager.getPathToPackage(pd, filename);
	}
	
	/**
	 * <p>
	 * Generate an HTML anchor that points to an {@link ExecutableMemberDoc}.
	 * Accepts {@link ConstructorDoc}, {@link MethodDoc}, and {@link FieldDoc}
	 * objects. Omits the leading "#" for href attributes since it is also used
	 * for DIV id attributes.
	 * </p>
	 * 
	 * <p>
	 * Method signatures require special attention in order to form valid XHTML.
	 * The matching anchors for links to methods are embedded in either "id" or
	 * "name" attributes of "a" tags, both of which have special rules. Namely,
	 * the values for these attributes can only contain these characters:
	 * </p>
	 * 
	 * <p>
	 * <code>[A-Za-z] [0-9] _ - : .</code>
	 * </p>
	 * 
	 * <p>
	 * Therefore, symbols that commonly occur in method signatures must be
	 * replaced, including:
	 * </p>
	 * 
	 * <p>
	 * <code>( ) [ ] &lt; &gt; ? , space</code>
	 * </p>
	 * 
	 * <p>
	 * There are a number of possible substitution schemes, but for simplicity,
	 * we use:
	 * </p>
	 * <code><ul>
	 * <li>"[]"  &nbsp; &nbsp; &nbsp; &nbsp; &rarr; ":"</li>
	 * <li>", "  &nbsp; &nbsp; &nbsp; &nbsp; &rarr; "-"</li>
	 * <li>"(" &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&rarr; "-"</li>
	 * <li>")" &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&rarr; ""</li>
	 * <li>" " &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&rarr; ""</li>
	 * <li>"&lt;" &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&rarr; ".:"</li>
	 * <li>"&gt;" &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&rarr; ":."</li>
	 * <li>"?" &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&rarr; "_"</li>
	 * <li>"? extends " &rarr; "_:"</li>
	 * </ul></code>
	 * 
	 * <p>
	 * Below are some examples of how method signatures are translated:
	 * </p>
	 * <ul>
	 * <li><code>add(int val1, int val2)</code><br />
	 * <code>add-int-int</code></li>
	 * <li><code>main(String[] args)</code><br />
	 * <code>main-java.lang.String:</code></li>
	 * <li><code>main(String key, Object... values)</code><br />
	 * <code>main-java.lang.String-java.lang.Object...</code></li>
	 * <li><code>method(List&lt;String&gt; list)</code><br />
	 * <code>method-java.util.List.:java.lang.String:.</code></li>
	 * <li><code>method(List&lt;?&gt;)</code><br />
	 * <code>method-java.util.List.:_:.</code> </li>
	 * <li><code>method(List&lt;? extends Doclet&gt;)</code><br />
	 * <code>method-java.util.List.:_:com.sun.javadoc.Doclet:.</code> </li>
	 * <li><code>method(Map&lt;String, List&lt;String&gt; &gt; nested)</code><br />
	 * <code>method-java.util.Map.:java.lang.String-java.util.List.:java.lang.String:.:.</code>
	 * </li>
	 * </ul>
	 * 
	 * @param member The class member for which to generate an anchor.
	 * @return An anchor to be placed in id attributes or after "#" in links.
	 */
	protected final String memberAnchor(MemberDoc member) {
		if (member instanceof ExecutableMemberDoc) {
			List<String> params = new Vector<String>();
			for (Parameter p : ((ExecutableMemberDoc) member).parameters()) {
				String param = p.type().toString();
				// Handle array delimiters
				param = param.replaceAll("\\[\\]", ":");
				// TODO Figure out how Varargs (...) is represented internally.
				// Handle parameter types delimiters
				param = param.replaceAll("<", ".:");
				param = param.replaceAll(">", ":.");
				// Handle parameter type wildcard
				param = param.replaceAll("\\?", "_");
				param = param.replaceAll(" extends ", ":");
				// Handle comma separations
				param = param.replaceAll(", ", "-");
				params.add(param);
			}
			// return target.name() + "-" + getJoinedStrings(params, "-");
			return member.name() + "-" + getJoinedStrings(params, "-");
		}
		return member.name(); // Accounts for fields and enums, etc.
	}
	
	/**
	 * Find the {@link PackageDoc} object for a given {@link Doc} object.
	 * 
	 * @param doc The object for which to find the package.
	 * @return The package that is or contains the given argument object.
	 */
	protected final PackageDoc packageForDoc(Doc doc) {
		if (doc == null)
			return null;
		else if (doc instanceof PackageDoc)
			return (PackageDoc) doc;
		else if (doc instanceof ProgramElementDoc)
			return ((ProgramElementDoc) doc).containingPackage();
		else
			throw Doclet.exception(new Exception("packageForDoc(" + doc.name()
					+ ")"), filename);
	}
	
	// TODO Implement -- replace getDocLink
	public final String linkToDoc(Doc doc) {
		return null;
	}
	
	/**
	 * Find the relative path to any {@link Doc} object. This depends on the
	 * value in {@link Config#currentPkg CONF.currentPkg}; if it is null, the
	 * path begins with {@link #pathToDocRoot} and the full path to the
	 * destination package, rather than the path between two packages.
	 * 
	 * <p>
	 * Throws a {@link DocletAbortException} if the parameter is not a
	 * {@link PackageDoc} or {@link ProgramElementDoc}.
	 * </p>
	 * 
	 * @param doc A javadoc element to find the path to.
	 * @return A relative path (or URL, if externally documented) to the Doc.
	 *         For packages, returns only a relative path to the directory;
	 *         filenames for summary, tree, use, etc. must then be appended.
	 * 
	 * @since 6 Jan 2008
	 * @see #pathBetweenPackages(String, String)
	 */
	protected final String hrefToDoc(Doc doc) {
		if (doc == null)
			return null;
		// Obtain package of specified Doc object
		PackageDoc pkg;
		if (doc instanceof PackageDoc) {
			pkg = (PackageDoc) doc;
		} else if (doc instanceof ProgramElementDoc) {
			pkg = ((ProgramElementDoc) doc).containingPackage();
		} else
			throw Doclet.exception(new Exception("hrefToDoc(" + doc.name()
					+ ")"), filename);
		
		// Get the base path from one package to another.
		String path;
		// Check if package is external (provide no relative path or filename).
		path = CONF.extern.getExternalLink(pkg.name(), "", "");
		if (path == null || path.equals("")) {
			// If not external, get the relative Javadoc path.
			if (CONF.currentPkg != null)
				path = pathBetweenPackages(CONF.currentPkg.name(), pkg.name());
			else
				path = pathToDocRoot + pathBetweenPackages("", pkg.name());
		}
		
		// Append the page name to link after the package path.
		// if (doc instanceof PackageDoc)
		// path += "package-summary" + CONF.ext;
		// else
		if (doc instanceof ClassDoc)
			path += doc.name() + CONF.ext;
		else if (doc instanceof MemberDoc) {
			// if (! ((MemberDoc)
			// doc).containingClass().equals(CONF.currentClass))
			path += ((MemberDoc) doc).containingClass().name() + CONF.ext;
			path += "#" + memberAnchor((MemberDoc) doc);
		}
		return path;
	}
	
	/**
	 * <p>
	 * Given two path names, finds the path from first to second. Uses
	 * tokenizers to compare the individual path elements and find the shortest
	 * relative path based on common package elements, rather than always going
	 * up to the document root and down. Example arguments and paths are below:
	 * </p>
	 * <table>
	 * <tr>
	 * <td><b>src</b></td>
	 * <td><b>dst</b></td>
	 * <td><b>path</b></td>
	 * </tr>
	 * <tr>
	 * <td>java.util</td>
	 * <td>java.util.concurrent</td>
	 * <td>concurrent/</td>
	 * </tr>
	 * <tr>
	 * <td>java.util.concurrent</td>
	 * <td>java.util</td>
	 * <td>../</td>
	 * </tr>
	 * <tr>
	 * <td>java.util.concurrent</td>
	 * <td>java.util.regex</td>
	 * <td>../regex</td>
	 * </tr>
	 * <tr>
	 * <td>java.util</td>
	 * <td>com.sun.javadoc</td>
	 * <td>../../com/sun/javadoc/</td>
	 * </tr>
	 * </table>
	 * 
	 * @param src The package to link from.
	 * @param dst The package to link to.
	 * @return The shortest relative path between src and dst, with trailing
	 *         slash. If the two arguments are equal, returns an empty string.
	 * 
	 * @since 6 Jan 2008
	 */
	protected final String pathBetweenPackages(String src, String dst) {
		if (src.equals(dst))
			return "";
		
		StringTokenizer srcTok = new StringTokenizer(src, ".");
		StringTokenizer dstTok = new StringTokenizer(dst, ".");
		String srcElement = null, dstElement = null, path = "";
		
		// Compare element pairs until one package path is exhausted.
		while (srcTok.hasMoreTokens() && dstTok.hasMoreTokens()) {
			srcElement = srcTok.nextToken();
			dstElement = dstTok.nextToken();
			// If elements don't match, prepend/append to relative path.
			if (!srcElement.equals(dstElement))
				path = "../" + path + dstElement + "/";
		}
		// For each remaining path element in src, prepend parent directory.
		while (srcTok.hasMoreTokens()) {
			path = "../" + path;
			srcTok.nextToken();
		}
		// For each remaining path element in dst, append child directory.
		while (dstTok.hasMoreTokens()) {
			path += dstTok.nextToken() + "/";
		}
		return path;
	}
	
	/**
	 * Retrieve the name of an element and create a formatted string.
	 * 
	 * @param doc The element from which to extract the name.
	 * @param asLink Whether the name should be embedded in a link tag.
	 * @param qualified Whether the reference should be fully qualified.
	 * @return A String with the element name, perhaps within a link.
	 * 
	 * @deprecated
	 */
	protected String getNameString(Doc doc, boolean asLink, boolean qualified) {
		String value = doc.name();
		if (qualified && doc instanceof ProgramElementDoc)
			value = ((ProgramElementDoc) doc).qualifiedName();
		if (!asLink)
			return value;
		// see if link to type exists, then create and return
		// if unable to link, always return qualified name
		return null;
	}
	
	/**
	 * Retrieve the type parameters for an entry.
	 * 
	 * @param doc The element from which to extract a TypeVariable array.
	 * @param asLinks Whether the names should be embedded in link tags.
	 * @return A list of the type parameters strings, perhaps with links; null
	 *         if <code>doc</code> is not a {@link ClassDoc} or
	 *         {@link ExecutableMemberDoc}.
	 */
	protected List<String> getTypeParameterStrings(ProgramElementDoc doc,
			boolean asLinks) {
		TypeVariable[] typeVariables;
		if (doc instanceof ClassDoc)
			typeVariables = ((ClassDoc) doc).typeParameters();
		else if (doc instanceof ExecutableMemberDoc)
			typeVariables = ((ExecutableMemberDoc) doc).typeParameters();
		else
			return null;
		
		List<String> typeParams = new Vector<String>();
		for (TypeVariable t : typeVariables) {
			// TODO Handle type variables, including bounds and wildcard types.
			typeParams.add(t.toString());
		}
		return typeParams;
	}
	
	/**
	 * Retrieve the parameters from an element and create a list of strings.
	 * 
	 * @param doc The element from which to extract the parameters.
	 * @param asLinks Whether the names should be embedded in link tags.
	 * @param qualified Whether the names should be in fully qualified form.
	 * @param withNames Whether to include names with formal typed parameters.
	 * @return A string list of the method parameters as specified. It will
	 *         never return null, only a zero-length list if no parameters.
	 */
	protected List<String> getParameterStrings(ExecutableMemberDoc doc,
			boolean asLinks, boolean qualified, boolean withNames) {
		List<String> params = new Vector<String>();
		String typeName;
		for (Parameter parameter : doc.parameters()) {
			if (parameter.type().isPrimitive())
				typeName = parameter.typeName();
			else {
				if (asLinks)
					typeName = linkToClass(parameter.type().asClassDoc());
				else
					typeName =
							(qualified)
									? parameter.type().qualifiedTypeName()
									: parameter.type().typeName();
				// TODO Print type parameters with links for parameterized
				// arguments
				typeName += parameter.type().dimension();
			}
			params.add(typeName + (withNames ? " " + parameter.name() : ""));
		}
		return params;
	}
	
	/**
	 * Given a method, lists parameters in parentheses, separated by commas.
	 * 
	 * @param doc {@link ExecutableMemberDoc} for which to list parameters.
	 * @param asLinks Whether the names should be embedded in link tags.
	 * @param qualified Whether the names should be in fully qualified form (<code>java.lang.Object</code>
	 *        versus <code>Object</code>).
	 * @param withNames Whether to include names with formal typed parameters (<code>boolean isActive</code>
	 *        versus <code>boolean</code>).
	 * @return <code>(type name{, type name}*)</code> if <i>withNames</i>.<br />
	 *         <code>(type{, type}*)</code> if <i>withNames</i> is false.<br />
	 *         <code>()</code> if there are 0 parameters.
	 * @see #getParameterStrings
	 */
	// FIXME Handle variable-arity parameter methods (Object... objects)
	// FIXME Handle nested generic parameters, including with wildcards.
	protected String getJoinedParameters(ExecutableMemberDoc doc,
			boolean asLinks, boolean qualified, boolean withNames) {
		return "("
				+ getJoinedStrings(getParameterStrings(doc, asLinks, qualified,
						withNames), ", ") + ")";
	}
	
	/**
	 * Given a method, lists parameters in parentheses, separated by commas. Has
	 * an added parameter to specify the separator to place between the strings.
	 * 
	 * @param doc {@link ExecutableMemberDoc} for which to list parameters.
	 * @param asLinks Whether the names should be embedded in link tags.
	 * @param qualified Whether the names should be in fully qualified form (<code>java.lang.Object</code>
	 *        versus <code>Object</code>).
	 * @param withNames Whether to include names with formal typed parameters (<code>boolean</code>
	 *        versus <code>boolean isActive</code>).
	 * @param separator The separator to be place between parameter strings.
	 * @return <code>(type name{&lang;separator&rang;type name}*)</code> if
	 *         <i>withNames</i>.<br />
	 *         <code>(type{&lang;separator&rang;type}*)</code> if <i>withNames</i>
	 *         is false.<br />
	 *         <code>()</code> if there are 0 parameters.
	 * @see #getParameterStrings
	 */
	protected String getJoinedParameters(ExecutableMemberDoc doc,
			boolean asLinks, boolean qualified, boolean withNames,
			String separator) {
		return "("
				+ getJoinedStrings(getParameterStrings(doc, asLinks, qualified,
						withNames), separator) + ")";
	}
	
	protected List<String> getExceptionStrings(ExecutableMemberDoc doc,
			boolean asLinks) {
		if (doc.thrownExceptions().length == 0)
			return null;
		List<String> exceptions = new Vector<String>();
		String exceptionName;
		for (ClassDoc exception : doc.thrownExceptions()) {
			if (asLinks)
				exceptionName = linkToClass(exception.asClassDoc());
			else
				exceptionName = exception.qualifiedTypeName();
			exceptions.add(exceptionName);
		}
		return exceptions;
	}
	
}
